package container

import (
	"hash/crc32"
	"io"
	"math"
	"os"
	"sync"
)

/**可以用于高效的ip校验
 * 注意：
 * 1.hash算法采用crc32,碰撞几率为4294967296分之一
 * 2.磁盘最大占用空间为512MB，调用Close方法会删除文件
 */
type Bitmap struct {
	file      *os.File
	useMemory bool
	bitmap    []byte
	mu        *sync.Mutex
}

func NewBitmap(file *os.File) *Bitmap {
	bitmap := &Bitmap{
		file: file,
		mu:   &sync.Mutex{},
	}

	//如果未传file，自动使用内存
	if file == nil {
		bitmap.useMemory = true
		bitmap.bitmap = make([]byte, math.MaxUint32/8)
	}

	return bitmap
}

func (this *Bitmap) getPosAndBitValue(value uint32) (filePosition int64, val byte) {
	pos := value % 8
	if pos == 0 {
		val = 1 << 7
	} else {
		val = 1 << (pos - 1)
	}

	return int64(value) / 8, val
}

func (this *Bitmap) readAt(position uint32) byte {
	if this.useMemory {
		return this.bitmap[position]
	}

	byt := make([]byte, 1)
	_, err := this.file.ReadAt(byt, int64(position))
	if err == io.EOF {
		err = nil
		return 0
	}

	if err != nil {
		return 0
	}
	return byt[0]
}

func (this *Bitmap) writeAt(byt byte, position uint32) bool {
	if this.useMemory {
		this.bitmap[position] = byt
		return true
	}
	_, err := this.file.WriteAt([]byte{byt}, int64(position))
	return err == nil
}

func (this *Bitmap) ValueExists(value uint32) (exists bool, err error) {
	position, val := this.getPosAndBitValue(value)
	byt := this.readAt(uint32(position))

	exists = byt&val == val
	return
}

func (this *Bitmap) KeyExists(key []byte) (exists bool, err error) {
	value := crc32.ChecksumIEEE(key)
	return this.ValueExists(value)
}

func (this *Bitmap) SaveValue(value uint32) (saved bool, err error) {
	position, val := this.getPosAndBitValue(value)

	this.mu.Lock()
	defer this.mu.Unlock()

	byt := this.readAt(uint32(position))
	saved = this.writeAt(val|byt, uint32(position))
	return
}

func (this *Bitmap) SaveKey(key []byte) (saved bool, err error) {
	value := crc32.ChecksumIEEE(key)
	return this.SaveValue(value)
}

func (this *Bitmap) Close() error {
	this.mu = nil
	this.bitmap = nil
	if !this.useMemory {
		return os.Remove(this.file.Name())
	}
	return nil
}
